package com.cmic.rcs.emoji.common.exception;

import com.cmic.rcs.emoji.common.UserContext;
import com.cmic.rcs.emoji.util.IpUtils;
import com.cmic.rcs.enhance.message.g5.core.common.Response;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.cmic.rcs.emoji.util.CommonUtils.exceptionSimpleDesc;
import static com.cmic.rcs.enhance.message.g5.core.common.ReturnCodeEnum.*;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;

/**
 * @author hzr
 * @since 1.0.0
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Response exceptionHandler(Exception e) {
        Throwable throwable = unwrapThrowable(e);
        log.error("未知异常: {}", throwable.toString(), throwable);
        return Response.Error(UNKNOWN_EXCEPTION);
    }

    @ExceptionHandler(BaseException.class)
    public Response baseExceptionHandler(BaseException e) {
        log.warn("自定义异常: {}", exceptionSimpleDesc(e));
        if (e.getCode() != 0) {
            return new Response(e.getCode(), e.getMessage());
        }
        return new Response(UNKNOWN_EXCEPTION.getCode(), e.getMessage());
    }

    @ExceptionHandler(NullPointerException.class)
    public Response nullPointerExceptionHandler(Exception e) {
        log.error("请求空指针异常: {}", exceptionSimpleDesc(e), e);
        return Response.Error(UNKNOWN_EXCEPTION);
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Response handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.warn("不支持的方法: {}", exceptionSimpleDesc(e));
        return new Response(MVC_EXCEPTION.getCode(), e.getMessage());
    }

    @ExceptionHandler({MissingServletRequestParameterException.class, ServletRequestBindingException.class})
    public Response mvcExceptionHandler(MissingServletRequestParameterException e) {
        log.error("请求参数不存在: {}", exceptionSimpleDesc(e));
        return Response.Error(MVC_EXCEPTION);
    }

    @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class})
    public Response handleHttpMessageNotReadableException(NestedRuntimeException e) {
        log.error("消息转换异常，请检查字段类型: ", e);
        return new Response(MVC_EXCEPTION.getCode(), "请求异常，请检查输入参数");
    }

    @ExceptionHandler({ConstraintViolationException.class, MethodArgumentNotValidException.class, ValidationException.class, BindException.class})
    public Response validationExceptionHandler(Exception e, HttpServletRequest request) {
        Set<String> errSet = new HashSet<>();
        if (e instanceof ConstraintViolationException) {
            ConstraintViolationException ex = (ConstraintViolationException) e;
            List<String> list = ex.getConstraintViolations()
                                  .stream()
                                  .map(ConstraintViolation::getMessage)
                                  .collect(Collectors.toList());
            errSet.addAll(list);
        } else if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
            errSet.addAll(buildBindingError(ex.getBindingResult()));
        } else if (e instanceof BindException) {
            BindException ex = (BindException) e;
            errSet.addAll(buildBindingError(ex.getBindingResult()));
        } else {
            errSet.add(e.getMessage());
        }

        log.warn("入参校验不通过: {}|{}|{}|{}|{}|{}|{}|{}", request.getMethod(), request.getRequestURI(), request.getQueryString(), IpUtils.getReal(request), request.getHeader("user-agent"), UserContext.getUserDto(), errSet, e.toString());

        if (errSet.isEmpty()) {
            return Response.Error(VERIFICATION_FAILED);
        }
        String firstException = errSet.stream().findFirst().get();
        if (firstException.contains(BaseException.class.getName())) {
            return new Response(MVC_EXCEPTION.getCode(), "请求异常，请检查输入参数");
        }
        return new Response(VERIFICATION_FAILED.getCode(), firstException);
    }

    private List<String> buildBindingError(BindingResult bindingResult) {
        return bindingResult.getAllErrors()
                            .stream()
                            .map(DefaultMessageSourceResolvable::getDefaultMessage)
                            .collect(Collectors.toList());
    }
}
